﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using XIPlugin;
using System.Windows.Forms;
using NLog;
using System.IO;
using System.Xml;

namespace AmmoTracker
{
    public class XIPlugin : XIPluginInterface, XIMultiInstancePlugin
    {
        /*
         * Creates a NLog logger for this class.  Try to use the correct
         * logging level when logging information (from lowest to highest):
         * Trace: The lowest level, use for fine detail.  i.e. entering/exiting logging
         * Debug: Information on the application flow.  Use for debugging info during
         * development.  This is the lowest level that appears in the console by default.
         * Info: Used for interesting runtime events.  Starting/stopped logging should go here
         * Warn: Used for runtime conditions that are bad, but not necessarily a dealbreaker.  
         * This is the lowest level that is logged to a file by default.
         * Error: Pretty obvious, these are serious problems.
         * 
         * The inclusion of NLog is to encourage thorough logging.  Ideally, all a user should
         * need to do in order to provide precise logs in the event of an error is to change to the 
         * appropriate logging level and send in their text log.
         */
        private static Logger logger = LogManager.GetCurrentClassLogger();

        /*
         * SettingsControl object, need to hang onto a pointer in order
         * to properly handle save calls
         */
        private SettingsControl settingsControl;
        private TabControl tabControl;

        /*
         * This dictionary holds on to the ffxi instance list, as a dictionary 
         * with the format:
         * [processId => FFXIWindow]
         */
        private Dictionary<int, FFXIWindow> windows;

        public class ItemData
        {
            public int flags;
            public int stackSize;
            public string name;
            public string full_name;
        }

        public class AmmoData
        {
            public uint main;
            public uint extra;
            public string abreviation;
            public string jobs;
            public AmmoData()
            {
                main = 0;
                extra = 0;
                abreviation = "";
                jobs = "ALL";
            }
        }
        private Dictionary<ushort, AmmoData> ammoTracking;
        public Dictionary<int, ItemData> itemDict;
        /*
         * The startPlugin call is used to setup any necessary data or 
         * callbacks for the plugin.  This is where you would setup
         * low level hardware callbacks for plugins with message support, or
         * initialize any fface/windower text fields
         */
        public void startPlugin(Form mainApp)
        {
            logger.Debug("Starting Ammo Tracker");
            itemDict = new Dictionary<int, ItemData>();

            /*
             * Created by Aureus
             */
            XmlTextReader reader = new XmlTextReader("Resources/armor.xml");
            parseItemList(reader);
            reader = new XmlTextReader("Resources/general.xml");
            parseItemList(reader);
            reader = new XmlTextReader("Resources/usable.xml");
            parseItemList(reader);
            reader = new XmlTextReader("Resources/weapons.xml");
            parseItemList(reader);
            // END

            SettingsModel settings = SettingsModel.getInstance();

            FFACE.CTHCreateTextObject(windows.First().Value.ffaceHandle, "AmmoCounter");
            RebuildFFACEText();
            FFACE.CTHFlushCommands(windows.First().Value.ffaceHandle);

            ammoTracking = new Dictionary<ushort, AmmoData>();

            Thread t = new Thread(MainLoop);
            t.Start();
        }

        private void CalculateInventory()
        {
            FFACE.INVENTORYITEM item = new FFACE.INVENTORYITEM();
            SettingsModel settings = SettingsModel.getInstance();

            foreach (ushort key in ammoTracking.Keys.ToList())
            {
                ammoTracking[key].main = 0;
                ammoTracking[key].extra = 0;
            }

            byte invMax = FFACE.GetInventoryMax(windows.First().Value.ffaceHandle);
            for (int i = 1; i <= invMax; i++)
            {
                item = FFACE.GetInventoryItem(windows.First().Value.ffaceHandle, i);

                if (ammoTracking.ContainsKey(item.ID))
                {
                    ammoTracking[item.ID].main += item.Count;
                }
            }

            if (settings.showSatchelCounts)
            {
                invMax = FFACE.GetSatchelMax(windows.First().Value.ffaceHandle);
                for (byte i = 1; i <= invMax; i++)
                {
                    item = FFACE.GetSatchelItem(windows.First().Value.ffaceHandle, i);

                    if (ammoTracking.ContainsKey(item.ID))
                    {
                        ammoTracking[item.ID].extra += item.Count;
                    }
                }
            }

        }

        bool exiting = false;
        private void MainLoop()
        {
            int cnt = 5;
            while (!exiting)
            {
                if (cnt++ == 10)
                {
                    MainEvent();
                    cnt = 1;
                }
                Thread.Sleep(100);
            }
        }

        private void MainEvent()
        {
            FFACE.PLAYERINFO plyr = new FFACE.PLAYERINFO();
            FFACE.GetPlayerInfo(windows.First().Value.ffaceHandle, ref plyr);
            FFACE.Job plyrJob = plyr.MainJob;

            CalculateInventory();
            SettingsModel settings = SettingsModel.getInstance();

            bool showEmpty = settings.showEmpty;

            string ammoText = "";
            string ammoName = "";

            bool firstLoop = true;
            foreach (KeyValuePair<ushort, AmmoData> invItem in ammoTracking)
            {
                if (invItem.Value.jobs.Contains(plyrJob.ToString()) || invItem.Value.jobs.Contains("ALL"))
                {
                    if (showEmpty || (!showEmpty && invItem.Value.main + invItem.Value.extra > 0))
                    {
                        if (firstLoop)
                            firstLoop = false;
                        else
                            ammoText += "\n";

                        string itemName = "";
                        if (itemDict.ContainsKey(invItem.Key))
                        {
                            if (invItem.Value.abreviation != "")
                                ammoName = invItem.Value.abreviation;
                            else
                                ammoName = itemDict[invItem.Key].name;

                            if (invItem.Value.extra > 0)
                                itemName = String.Format("{0}: {1} ({2})", ammoName, invItem.Value.main, invItem.Value.extra);
                            else
                                itemName = String.Format("{0}: {1}", ammoName, invItem.Value.main);
                        }
                        else
                            itemName = "Invalid ID: " + invItem.Key.ToString();

                        ammoText += itemName;
                    }
                }
            }
            FFACE.CTHSetText(windows.First().Value.ffaceHandle, "AmmoCounter", ammoText);
            FFACE.CTHFlushCommands(windows.First().Value.ffaceHandle);
        }

        private void RebuildFFACEText()
        {
            SettingsModel settings = SettingsModel.getInstance();

            FFACE.CTHSetBGColor(windows.First().Value.ffaceHandle, "AmmoCounter", (byte)settings.bgColourAlpha, (byte)settings.bgColourRed, (byte)settings.bgColourGreen, (byte)settings.bgColourBlue);
            FFACE.CTHSetBGVisibility(windows.First().Value.ffaceHandle, "AmmoCounter", true);
            FFACE.CTHSetColor(windows.First().Value.ffaceHandle, "AmmoCounter", (byte)settings.textColourAlpha, (byte)settings.textColourRed, (byte)settings.textColourGreen, (byte)settings.textColourBlue);
            FFACE.CTHSetLocation(windows.First().Value.ffaceHandle, "AmmoCounter", settings.positionX, settings.positionY);
            FFACE.CTHSetFont(windows.First().Value.ffaceHandle, "AmmoCounter", "Arial", (short)settings.textSize);
            FFACE.CTHSetText(windows.First().Value.ffaceHandle, "AmmoCounter", "Ammo Counter");
            FFACE.CTHFlushCommands(windows.First().Value.ffaceHandle);
        }

        /*
         * Plugin name.
         */
        public string Name
        {
            get
            {
                return "Ammo Tracker";
            }
        }

        /*
         * This method is called in different contexts depending on the
         * plugin type.  For a multi instance plugin, this is called every time a 
         * ffxi window is created or exits, along with on creation.
         * For a single instance plugin, this is only called at creation.
         */
        public void setPluginContext(XIPluginContext context)
        {
            logger.Trace("Plugin Context updated");
            windows = context.windows;
        }

        /*
         * This method is called when the plugin needs to terminate.  
         * If the plugin thread is still active some time after calling this, 
         * it will be force stopped.  Clean up any windower or fface
         * text boxes here, take care of any timers, etc.
         */
        public void stopPlugin()
        {
            exiting = true;

            FFACE.CTHDeleteTextObject(windows.First().Value.ffaceHandle, "AmmoCounter");
            FFACE.CTHFlushCommands(windows.First().Value.ffaceHandle);

            tabControl.SaveAmmoList();
        }

        /*
         * This returns the keyboard command associated with this plugin.  Any 
         * windower input matching this command will be sent to the processCommand 
         * method in this plugin.  If no keyboard command is necessary for a plugin,
         * return null from this method.
         */
        public string Command
        {
            get
            {
                return "ammo";
            }
        }

        /*
         * This method is called when a matching keyboard command is found.  
         * For example, the command for this plugin is "simple", typing
         * "//simple test 1 2 3" into the chatline will call this method with a
         * XICommandContext object with args of ["test", "1", "2", "3"]
         * It is important to note that processCommand is called in a new
         * thread, so be sure to use threadsafe access to data
         */
        public void processCommand(object args)
        {
            XICommandContext xic = (XICommandContext)args;
            List<String> commandArgs = xic.args;

            logger.Debug("CommandArgs from pid " + xic.pid + ": ");
            foreach (String str in commandArgs)
            {
                logger.Debug(str);
            }

        }

        /*
         * Plugin description, this shows up in the plugin selection box
         */
        public string Description
        {
            get
            {
                return "This plugin will allow you to track ammo and other items on screen.";
            }
        }

        /*
         * This should return the display tab of the plugin, if one is needed.
         * For plugins that do not require a display tab, return null.
         */
        public TabPage DisplayTab
        {
            get
            {
                if (tabControl == null || tabControl.IsDisposed)
                {
                    tabControl = new TabControl(ref ammoTracking, ref itemDict);
                    foreach (KeyValuePair<int, ItemData> dictItem in itemDict)
                    {
                        if (dictItem.Value.name != ".")
                        {
                            tabControl.itemsComboBox.Items.Add(new KeyValuePair<int, string>(dictItem.Key, dictItem.Value.name));
                            tabControl.itemsComboBox.DisplayMember = "Value";
                            tabControl.itemsComboBox.ValueMember = "Key";
                        }
                    }
                }
                TabPage t = new TabPage(Name);
                t.Controls.Add(tabControl);
                return t;
            }
        }

        /*
         * This should return the settings tab of the plugin, if one is needed.
         * For plugins that do not require a settings tab, return null.
         */
        public TabPage SettingsTab
        {
            get
            {
                if (settingsControl == null || settingsControl.IsDisposed)
                {
                    settingsControl = new SettingsControl();
                }
                TabPage t = new TabPage(Name);
                t.Controls.Add(settingsControl);
                return t;
            }
        }

        /*
         * This method is called when settings should be saved.  It is expected that
         * the plugin will read in settings from the SettingsTab, and save them as necessary.
         */
        public void saveSettings()
        {
            SettingsModel settings = SettingsModel.getInstance();

            settings.positionX = Convert.ToInt32(settingsControl.positionX.Text);
            settings.positionY = Convert.ToInt32(settingsControl.positionY.Text);
            settings.textSize = Convert.ToInt32(settingsControl.textSize.Text);
            settings.textColourRed = Convert.ToInt32(settingsControl.textColourR.Text);
            settings.textColourGreen = Convert.ToInt32(settingsControl.textColourG.Text);
            settings.textColourBlue = Convert.ToInt32(settingsControl.textColourB.Text);
            settings.textColourAlpha = Convert.ToInt32(settingsControl.textColourA.Text);
            settings.bgColourRed = Convert.ToInt32(settingsControl.bgColourR.Text);
            settings.bgColourGreen = Convert.ToInt32(settingsControl.bgColourG.Text);
            settings.bgColourBlue = Convert.ToInt32(settingsControl.bgColourB.Text);
            settings.bgColourAlpha = Convert.ToInt32(settingsControl.bgColourA.Text);
            settings.showEmpty = settingsControl.showEmpty.Checked;
            settings.showSatchelCounts = settingsControl.showSatchelCounts.Checked;
            //settings.showSackCounts = settingsControl.showSackCounts.Checked;

            logger.Debug("Saving settings");
            settings.SaveSettings();

            RebuildFFACEText();
        }

        /*
         * Created by Aureus
         */
        private void parseItemList(XmlTextReader reader)
        {
            bool isId = false;
            bool isStack = false;
            bool isFlags = false;
            bool isGraphic = false;
            bool isName = false;
            bool isFullName = false;
            bool isType = false;

            int id = 0;
            int stack = 0;
            int flags = 0;
            int item_type = 0;
            string name = "";
            string full_name = "";

            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element: // The node is an element.
                        if (reader.Name.Equals("field"))
                        {
                            // Read the attributes.
                            while (reader.MoveToNextAttribute())
                            {
                                if (reader.Name.Equals("name"))
                                {
                                    if (reader.Value.Equals("id"))
                                    {
                                        isId = true;
                                        isStack = false;
                                        isFlags = false;
                                        isName = false;
                                        isFullName = false;
                                        isType = false;
                                    }
                                    else if (reader.Value.Equals("flags"))
                                    {
                                        isId = false;
                                        isStack = false;
                                        isFlags = true;
                                        isName = false;
                                        isFullName = false;
                                        isType = false;
                                    }
                                    else if (reader.Value.Equals("stack-size"))
                                    {
                                        isId = false;
                                        isStack = true;
                                        isFlags = false;
                                        isName = false;
                                        isFullName = false;
                                        isType = false;
                                    }
                                    else if (reader.Value.Equals("name"))
                                    {
                                        isId = false;
                                        isStack = false;
                                        isFlags = false;
                                        isName = true;
                                        isFullName = false;
                                        isType = false;
                                    }
                                    else if (reader.Value.Equals("log-name-singular"))
                                    {
                                        isId = false;
                                        isStack = false;
                                        isFlags = false;
                                        isName = false;
                                        isFullName = true;
                                        isType = false;
                                    }
                                    else if (reader.Value.Equals("type"))
                                    {
                                        isId = false;
                                        isStack = false;
                                        isFlags = false;
                                        isName = false;
                                        isFullName = false;
                                        isType = true;
                                    }

                                }
                            }
                        }
                        else if (reader.Name.Equals("thing"))
                        {
                            while (reader.MoveToNextAttribute())
                            {
                                if (reader.Name.Equals("type"))
                                {
                                    if (reader.Value.Equals("Graphic"))
                                    {

                                        isGraphic = true;
                                    }
                                }
                            }
                        }
                        break;
                    case XmlNodeType.Text: //Display the text in each element.
                        if (isGraphic)
                            break;
                        if (isId)
                        {
                            id = Int32.Parse(reader.Value);
                        }
                        else if (isStack)
                        {
                            stack = Int32.Parse(reader.Value);
                        }
                        else if (isFlags)
                        {
                            flags = Int32.Parse(reader.Value, System.Globalization.NumberStyles.HexNumber);
                        }
                        else if (isName)
                        {
                            name = reader.Value;
                        }
                        else if (isFullName)
                        {
                            full_name = reader.Value;
                        }
                        else if (isType)
                        {
                            item_type = Int32.Parse(reader.Value, System.Globalization.NumberStyles.HexNumber);
                        }
                        break;
                    case XmlNodeType.EndElement: //Display the end of the element.
                        if (reader.Name.Equals("thing"))
                        {
                            ItemData item = new ItemData();
                            item.flags = flags;
                            item.stackSize = stack;
                            item.name = name;
                            item.full_name = full_name;
                            itemDict[id] = item;
                            isGraphic = false;
                        }
                        else if (reader.Name.Equals("field"))
                        {
                            isId = false;
                            isStack = false;
                            isFlags = false;
                            isName = false;
                            isFullName = false;
                            isType = false;
                        }
                        break;
                }
            }
        }


    }
}
